'''
Created on 08-02-2012

@author: cleve
'''

from random import randint
from math import sqrt
import numpy
import time

class MatrixClass(object):
    '''
    Clase encargada de crear y realizar las modificaciones en la matriz Espacio-Tiempo.
    
    Existen metodos separados para el TSP y PM, debido a la naturaleza de cada problema.
    
    TSP: Es un grafo completo, por lo tanto se necesita modelar una matriz de nxn elementos.
    PM: Es una matriz de 1xn, se realizo un metodo independiente para optimizar el proceso.
    '''
    
    def __init__(self, numNodos, matrizDistancia, numeroParticulas):
        
        self.nodos = numNodos
        self.numeroCalculos = int(numNodos)
        
        #Seleccion de energia oscura en universo
        #self.energiaOscura = randint(1, int(sqrt(self.nodos)))
        self.energiaOscura = 2
        
        #Maximo de entrada en grafo.
        self.maximoEspacioTiempo = int(numpy.array(matrizDistancia).max())
        self.minimoEspacioTiempo = int(numpy.array(matrizDistancia).min())
        self.promedioMasaEspacioTiempo = int(numpy.average(numpy.array(matrizDistancia)))
        self.sumaEspacioTiempo = numpy.sum(numpy.array(matrizDistancia))
    
    
    def Inflacion(self, dimension):
        """ Creacion aleatoria del plano E-T, curvandolo en ciertas areas.
        Se denomina BigBang porque es el estado inicial del universo. Se considera inflacion.
    
        @param PARAM: Plano
        @return RETURN: Plano curvado
        """
        if dimension[0] == 1:        
            
            #Creo matriz con unos.
            curvatura = numpy.ones(self.nodos, int)
            
            #return numpy.random.randint(1, self.maximoEspacioTiempo, dimension)[0]
            for procesoInflacion in xrange(int(self.nodos*0.3)):
                #Masa disponible para realizar inflacion en el espacio.
                masaDistribuida = randint(2,self.promedioMasaEspacioTiempo)
                
                pos = randint(0, self.nodos - 1)
                
                curvatura[pos] = masaDistribuida                
            
            return numpy.random.randint(1, self.promedioMasaEspacioTiempo, dimension)
        
        else:
            #Creo matriz con unos.
            curvatura = numpy.ones(dimension, int)
            
            #Generar masas
            for procesoInflacion in xrange(int(self.nodos*0.3)):
                #Masa disponible para realizar inflacion en el espacio.
                masaDistribuida = randint(2,self.promedioMasaEspacioTiempo)
                
                fila = randint(0, self.nodos - 1)
                columna = randint(0, self.nodos -1)
                
                curvatura[fila][columna] = masaDistribuida                
            
            return numpy.random.randint(1, self.promedioMasaEspacioTiempo, dimension)
    
    
    def BigBang(self, dimension):
        """ Creacion aleatoria del plano E-T, curvandolo en ciertas areas.
        Se denomina BigBang porque es el estado inicial del universo, no considera
        la inflacion.
    
        @param PARAM: Plano
        @return RETURN: Plano curvado
        """
        if dimension[0] == 1:        
            return numpy.random.randint(1, self.maximoEspacioTiempo, dimension)[0]
        
        else:
            return self.Inflacion(dimension)
            #return numpy.random.randint(1, self.promedioMasaEspacioTiempo, dimension)
  
    def EnergiaOscuraTSP(self, curvatura):
        """ La energia oscura permite que el universo se expanda
        aceleradamente.
    
        @param curvatura: Plano en estado actual
        @return curvatura: Plano curvado segun efecto de repulsion.
        """
        #print 'antes'
        #print curvatura
        
        for numeroExpansiones in xrange(self.nodos/2):
            
            indiceBusqueda = randint(0,self.nodos-1)
            
            if list(curvatura[indiceBusqueda]).count(1) > 2:
                
                #Contador de espacio vacio.
                espacioVacio = 0
        
                #Busco posicion de primer elemento con espacio vacio.
                indiceVacioColumna = list(curvatura[indiceBusqueda]).index(1, 0, self.nodos-2)
            
                #Expando el area seleccionada desde centro hasta espacio superior.
                for posColumna in xrange(indiceVacioColumna, self.nodos - 1):
                    if posColumna == self.nodos - 1:
                        break
                    
                    if curvatura[indiceBusqueda][posColumna] == 1:
                        espacioVacio+=1
                        
                    if curvatura[indiceBusqueda][posColumna] != 1:
                        espacioVacio = 1
                        break
                        
                    
                    if espacioVacio >= self.energiaOscura:
                        '''
                        Verificacion desde zonas candidatas a repulsion hasta fin,
                        en esta etapa se repele la masa vecina hasta encontrar espacio
                        vacio. En caso contrario se elimina del espacio.
                        '''
                        for indiceFuerza in xrange(posColumna, self.nodos-2):
                            if curvatura[indiceBusqueda][indiceFuerza] == 1:
                                pass
                            
                            elif curvatura[indiceBusqueda][indiceFuerza] != 1:
                                if curvatura[indiceBusqueda][indiceFuerza + 1] == 1:
                                    curvatura[indiceBusqueda][indiceFuerza + 1] += curvatura[indiceBusqueda][indiceFuerza]
                                    curvatura[indiceBusqueda][indiceFuerza] = 1
                                    break
                                    
                                else:
                                    curvatura[indiceBusqueda][indiceFuerza] = 1
                                    break

            else:
                pass
       
        #print 'despues:'
        #print curvatura
        #time.sleep(10)
        return curvatura
    
    def EnergiaOscuraMochila(self, curvatura):
        """ La energia oscura permite que el universo se expanda
        aceleradamente.
    
        @param curvatura: Plano en estado actual
        @return curvatura: Plano curvado segun efecto de repulsion.
        """
        
        try:
            randomIndice = randint(0, self.nodos -1)
            randomIndiceFin = randint(randomIndice, self.nodos -1)
            continuar = True
            espacioVacio = 1
            
            #Busco indice que contenga espacio vacio.
            indiceVacio =  list(curvatura)[randomIndice:randomIndiceFin].index(1) + randomIndice
            
            #Si se encuentran tres regiones vacias en el espacio aplicar fuerza de repulsion.
            for indice in xrange(indiceVacio + 1, randomIndiceFin):
                if indice == self.nodos - 1 or continuar == False:
                    break
                
                if curvatura[indice] == 1:
                    espacioVacio +=1
                    
                elif curvatura[indice] != 1:
                    espacioVacio = 0
                    break
                    
                #Verificacion de espacio y repulsion de fuerza.    
                #if espacioVacio >= self.energiaOscura and curvatura[indice] == 1:
                if espacioVacio >= self.energiaOscura and curvatura[indice] == 1:
                    
                    '''
                    Verificacion desde zonas candidatas a repulsion hasta fin,
                    en esta etapa se repele la masa vecina hasta encontrar espacio
                    vacio. En caso contrario se elimina del espacio.
                    '''
                    for indiceFuerza in xrange(indice, self.nodos-2):
                        if curvatura[indiceFuerza] == 1:
                            pass
                        
                        elif curvatura[indiceFuerza] != 1:
                            if curvatura[indiceFuerza + 1] == 1:
                                curvatura[indiceFuerza + 1] += curvatura[indiceFuerza]
                                curvatura[indiceFuerza] = 1
                                break
                                
                            else:
                                curvatura[indiceFuerza] = 1
                                break
                            
        except:
            #Si no encuentra espacio vacio no hacer nada. Esto puede ocurrir en el BigBang.
            pass
        
        return curvatura
        
    
    def ModificarCurvaturaMochila(self, curvatura):
        """ Curvar el plano segun la cantidad de materia
    
        @param curvatura: Plano en estado actual
        @return curvatura: Plano curvado segun cuerpos masivos
        """
        
        curvatura = self.EnergiaOscuraMochila(curvatura)
        
        for modificacion in xrange(self.numeroCalculos):
            indice = randint(1, self.nodos - 2)
            
            centro = curvatura[indice]
            centroMaximo = curvatura[modificacion]
            iz = curvatura[indice - 1]
            dere = curvatura[indice + 1]
            
            #Verificar limite maximo masivo permitido.
            if centroMaximo != 1:
                if centro > self.maximoEspacioTiempo:
                    masaNueva = centroMaximo / 3
                    curvatura[indice] = masaNueva
                    curvatura[randint(0,self.nodos-1)] +=  masaNueva
                    curvatura[randint(0,self.nodos-1)] +=  masaNueva
            
            #Interaccion gravitacional.
            if dere != 1 and iz != 1 and centro != 1:
                
                if centro > iz and centro > dere:
                    
                    curvatura[indice] = centro + iz + dere
                    curvatura[indice - 1] = 1
                    curvatura[indice + 1] = 1
                    
                elif centro > iz and centro < dere:
                    curvatura[indice] = centro + iz
                    curvatura[indice - 1] = 1
                
                elif centro < iz and centro > dere:
                    curvatura[indice] = centro + dere
                    curvatura[indice + 1] = 1
                
                else:
                    pass

        return curvatura
    
    
    def modificar_curvatura(self, curvatura):
        """ Curvar el plano segun la cantidad de materia
    
        @param curvatura: Plano en estado actual
        @return curvatura: Plano curvado segun cuerpos masivos
        """
        
        curvatura = self.EnergiaOscuraTSP(curvatura)
        
        # Donde: i filas, j columnas
        for interacciones in xrange(self.numeroCalculos):
            while 1:
                i = randint(1, self.nodos - 2)
                j = randint(1, self.nodos - 2)
                if i != j:
                    break
              
            
        #Vecindad moore
        centro = curvatura[i][j]
        a_iz = curvatura[i - 1][j - 1]
        iz = curvatura[i - 1][j]
        aba_iz = curvatura[i - 1][j + 1]
        aba = curvatura[i][j + 1]
        aba_dere = curvatura[i + 1][j + 1]
        dere = curvatura[i + 1][j]
        a_dere = curvatura[i + 1][j - 1]
        a = curvatura[i][j - 1]         
          
        
        if centro >= self.maximoEspacioTiempo:
            masa_nueva = centro / 9
            
            curvatura[i][j] = masa_nueva
            
            for explosion in xrange(8):
                ii = randint(0, self.nodos - 1)
                jj = randint(0, self.nodos - 1)
                curvatura[ii][jj] = masa_nueva
            pass
      
          
        elif centro > a_iz and a_iz != 1 and a_iz != 0:
            curvatura[i][j] += a_iz
            curvatura[i - 1][j - 1] = curvatura[j - 1][i - 1] = 1
            
        elif centro > iz and iz != 1 and iz != 0:
            curvatura[i][j] += iz
            curvatura[i - 1][j] = curvatura[i][j - 1] = 1
            
        elif centro > aba_iz and aba_iz != 1 and aba_iz != 0:
            curvatura[i][j] += aba_iz
            curvatura[i - 1][j + 1] = curvatura[i + 1][j - 1] = 1
            
        elif centro > aba and aba != 1 and aba != 0:
            curvatura[i][j] += aba
            curvatura[i][j + 1] = curvatura[j + 1][j] = 1
            
        elif centro > aba_dere and aba_dere != 1 and aba_dere != 0:
            curvatura[i][j] += aba_dere
            curvatura[i + 1][j + 1] = curvatura[i + 1][j + 1] = 1
            
        elif centro > dere and dere != 1 and dere != 0:
            curvatura[i][j] += dere
            curvatura[i + 1][j] = curvatura[i][j + 1] = 1
            
        elif centro > a_dere and a_dere != 1 and a_dere != 0:
            curvatura[i][j] += a_dere
            curvatura[i + 1][j - 1] = curvatura[i - 1][j + 1] = 1
          
        elif centro > a and a != 1 and a != 0:
            curvatura[i][j] += a
            curvatura[i][j - 1] = curvatura[i - 1][j] = 1
        
        else:
            pass
        
        return curvatura
    
    
